#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include "Comm.hpp"

#define CREATER "creater"
#define USER "user"

const int gdefaultid = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 114;
const int gmode = 0666;

class SharedMemory
{
private:
    // 创建一个全新的共享内存段

    void CreateHelper(int flg)
    {
        printf("key: 0x%x\n", _key);
        _shmid = shmget(_key, _size, flg);
        if (_shmid == -1)
        {
            ERR_EXIT("shmget");
        }
        printf("shmid: %d\n", _shmid);
    }

    void Create()
    {
        // IPC_CREAT | IPC_EXCL：强制创建全新共享内存，若已存在直接报错
        CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
    }

    void Get()
    {
        // 只传IPC_CREAT：共享内存已存在则直接获取
        CreateHelper(IPC_CREAT);
    }

    void Attach()
    {
        if (_shmid == gdefaultid)
        {
            return;
        }

        // 进程与共享内存"建立连接"：把内核的共享内存映射到当前进程地址空间
        _start_mem = shmat(_shmid, nullptr, 0);
        if (_start_mem == (void *)-1)
        {
            ERR_EXIT("shmat");
        }
        printf("attach success, VirtualAddr: %p\n", _start_mem);
    }

    // 作用：进程与共享内存"断开连接"（解除地址映射）
    void Detach()
    {
        if (_start_mem != nullptr)
        {
            int n = shmdt(_start_mem); // 关键系统调用：分离映射
            if (n == 0)
            {
                printf("detach success\n");
                _start_mem = nullptr; // 重置指针，避免重复操作
            }
            else
            {
                ERR_EXIT("shmdt");
            }
        }
    }

    void Destory()
    {
        if (_shmid == gdefaultid)
        {
            return;
        }

        // 为什么要先Detach？
        // 1. 共享内存是内核管理的，进程不主动断开，内核认为进程还在使用它
        // 2. 就算调用IPC_RMID标记删除，内核也不会真的释放（怕进程还在读写）
        // 3. 残留的共享内存会导致下次启动server时，shmget报"File exists"
        // 4. 残留内存的权限可能异常，导致client attach时"Permission denied"
        Detach();

        // 标记共享内存为删除：所有进程都断开连接后，内核会彻底释放
        if (shmctl(_shmid, IPC_RMID, nullptr) == -1)
        {
            ERR_EXIT("shmctl");
        }
        printf("shm removed shm: %d\n", _shmid);
        _shmid = gdefaultid;
    }

public:
    SharedMemory(const std::string &pathname, int projid, const std::string &usertype, int size = gsize)
        : _shmid(gdefaultid), _size(size), _start_mem(nullptr), _usertype(usertype)
    {
        // 生成一个唯一的键值, 只给内核进行区分的唯一性
        _key = ftok(pathname.c_str(), projid);
        if (_key == -1)
        {
            ERR_EXIT("ftok");
        }
        printf("key = 0x%x\n", _key);

        if (_usertype == CREATER)
        {
            Create();
        }
        else if (_usertype == USER)
        {
            Get();
        }
        else
        {
            printf("usertype error");
            exit(EXIT_FAILURE);
        }
        Attach(); // 构造时自动建立连接
    }

    int Size() const
    {
        return _size;
    }

    void *StartMem() const
    {
        return _start_mem;
    }

    // 新增：共享内存属性查询接口
    void Attr() const
    {
        if (_shmid == gdefaultid)
        {
            printf("shm not initialized\n");
            return;
        }
        struct shmid_ds ds;
        if (shmctl(_shmid, IPC_STAT, &ds) == -1)
        {
            ERR_EXIT("shmctl(IPC_STAT)");
        }
        printf("shm_segsz: %ld\n", ds.shm_segsz); // 共享内存大小
        printf("key: 0x%x\n", ds.shm_perm.__key); // 共享内存key
        printf("shm_cpid: %d\n", ds.shm_cpid);    // 创建者PID（额外补充）
    }

    void Remove()
    {
        if (_usertype == CREATER)
            Destory();
    }

    ~SharedMemory()
    {
        if (_usertype == CREATER)
            Destory();
    }

private:
    int _shmid;            // 共享内存ID（内核分配）
    key_t _key;            // 共享内存唯一标识（ftok生成）
    int _size;             // 共享内存大小
    void *_start_mem;      // 共享内存在当前进程的虚拟地址（attach后得到）
    std::string _usertype; // 进程类型（创建者/使用者）
};
